--[[
Name: LibCooldown-Spell-2.0
Revision: $REVISION$
Author(s): Sirow (EU-Gorgonnash)
Website: http://code.google.com/p/sirow/
Description: Register spell cooldowns to more descriptive events
Dependencies: LibStub, CallbackHandler-1.0, LibSimpleTimer-1.0, LibBabble-Zone-3.0
]]


-------------------------------------------------------------------------------
-- Library - Version Info & Dependencies
-------------------------------------------------------------------------------
-- Version
local MAJOR = "LibCooldown-Spell-2.0"
local MINOR = tonumber(("$Revision: 11 $"):match("(%d+)")) + 90000

-- Dependencies
if not LibStub 						  	then error(MAJOR .. " requires LibStub.") 				end
if not LibStub("LibSimpleTimer-1.0")  	then error(MAJOR .. " requires LibSimpleTimer-1.0.") 	end
if not LibStub("CallbackHandler-1.0") 	then error(MAJOR .. " requires CallbackHandler-1.0.") 	end
if not LibStub("LibGratuity-3.0") 		then error(MAJOR .. " requires LibGratuity-3.0.") 		end
if not LibStub("LibBabble-Zone-3.0") 	then error(MAJOR .. " requires LibBabble-Zone-3.0.") 	end


-------------------------------------------------------------------------------
-- Library - Register
-------------------------------------------------------------------------------
-- Register
local LibCooldownSpell 	= LibStub:NewLibrary(MAJOR, MINOR)
if not LibCooldownSpell then return end

-- Embed CallbackHandler and LibSimpleTimer
LibCooldownSpell.events = LibCooldownSpell.events or LibStub("CallbackHandler-1.0"):New(LibCooldownSpell)
local SimpleTimer		= LibStub("LibSimpleTimer-1.0")
local Gratuity 			= LibStub("LibGratuity-3.0")
local BabbleZone		= LibStub("LibBabble-Zone-3.0"):GetReverseLookupTable()

-- SpeedUpz
local GetSpellName		= GetSpellName
local GetSpellCooldown	= GetSpellCooldown


-------------------------------------------------------------------------------
-- Library - Localization
-------------------------------------------------------------------------------
-- Translation
local L = {}

-- enUS locale
L["second"]	= "sec"

-- deDE locale
if (GetLocale() == "deDE") then
	L["second"]	= "sek"
end

-- esES locale
if (GetLocale() == "esES") then
end

-- frFR locale
if (GetLocale() == "frFR") then
end

-- koKR locale
if (GetLocale() == "koKR") then
end

-- zhCN locale
if (GetLocale() == "zhCN") then
end

-- zhTW locale
if (GetLocale() == "zhTW") then
end


-------------------------------------------------------------------------------
-- Library - Variables
-------------------------------------------------------------------------------	
-- Event handling
LibCooldownSpell.frame = LibCooldownSpell.frame or CreateFrame("Frame")
local frame = LibCooldownSpell.frame
frame:UnregisterAllEvents()

-- Minimal duration for spells to track (in seconds, only works with duration lower than 60 seconds!)
local minDuration 	= 3
local lastUpdate	= 0
local updateDelay	= 3

-- Spells in this table get monitored
local spellCache 	= {
	[BOOKTYPE_SPELL]	= {},
	[BOOKTYPE_PET]		= {},
}
local sheduledSpells	= {
	[BOOKTYPE_SPELL]	= {},
	[BOOKTYPE_PET]		= {},
}

-- Startup/disable stuff
local noCache 		= {
	[BOOKTYPE_SPELL]	= true,
	[BOOKTYPE_PET]		= true,
}
local hasStartCallback		= false
local hasStopCallback		= false
local hasPetStartCallback	= false
local hasPetStopCallback	= false

-- Arena, need to reset timer here
local arenas = {
	[BabbleZone["Blade's Edge Arena"]]	= true,
	[BabbleZone["Nagrand Arena"]] 		= true,
	[BabbleZone["Ruins of Lordaeron"]]	= true,
}


-------------------------------------------------------------------------------
-- Library - Core
-------------------------------------------------------------------------------
local ScanSpells, UpdateSpellCooldown, SpellFound, RelayEndEvent, CheckZoneReset
do


-- Scan speelbook for spells that have a cooldown
ScanSpells = function (bookType)
	-- Pre-loop stuff
	local i = 1
	local spellName_prev, tooltipText, cooldown
	local spellName = GetSpellName(i, bookType)
	
	-- Scan through every spell in spellbook
	while spellName do
		-- Prevent rescanning of spells with the same name (lets assume ALL have the same CD!)
		if (spellName ~= spellName_prev) then
			-- Scan spell tooltip with gratuity to find cooldown
			Gratuity:SetSpell(i, bookType)
			
			-- Found cooldown text?
			if (Gratuity:Find("cooldown", 2, 3, true)) then	
				-- Fetch Text
				tooltipText = Gratuity:GetText(2 , 3 , true)
				
				-- Scan for cooldown length
				if (tooltipText[1] and tooltipText[1][2] and tooltipText[1][2]:find("cooldown")) then
					cooldown = string.match(tooltipText[1][2], "(%d+)")
					
					-- If its to short, than ignore it
					if (tooltipText[1][2]:find(L["second"]) and tonumber(cooldown) < minDuration) then
						cooldown = false
					else 
						cooldown = true
					end
				-- Scan for cooldown length
				elseif (tooltipText[2] and tooltipText[2][2] and tooltipText[2][2]:find("cooldown")) then
					cooldown = string.match(tooltipText[2][2], "(%d+)")
					
					-- If its to short, than ignore it
					if (tooltipText[2][2]:find(L["second"]) and tonumber(cooldown) < minDuration) then
						cooldown = false
					else 
						cooldown = true
					end
				end
				
				-- Only save those with long enough cooldown
				if (cooldown) then
					-- Save of just update spell info 
					if (spellCache[bookType][spellName]) then
						spellCache[bookType][spellName].spellID = i
					else
						spellCache[bookType][spellName] = {
							spellID = i,
							startTime = 0,
						}
					end
				end
				
			-- For some reason, this spell lost his cooldown?!
			elseif (spellCache[bookType][spellName]) then
				spellCache[bookType][spellName] = nil
			end
		end
	
		-- Set values for next loop
		i 				= i + 1
		spellName_prev 	= spellName
		spellName 		= GetSpellName(i, bookType)
	end
	
	-- Also scan if any cooldown updated
	if (not noCache[bookType]) then
		noCache[bookType] = false
		UpdateSpellCooldown(bookType)
	else
		noCache[bookType] = false
	end
end


-- Make sure its still the right spell and it really has a cooldown
SpellFound = function (bookType, spellName)
	-- Test if spell is still at this spellID
	if (GetSpellName(spellCache[bookType][spellName].spellID, bookType) == spellName) then
		return true
	else
		ScanSpells(bookType)
		
		-- Spell found or not?
		if (spellCache[bookType][spellName]) then
			return true
		else
			return false
		end
	end
end


-- Update all cooldowns
UpdateSpellCooldown = function (bookType)
	-- Fetch spell-infos once
	if (noCache[bookType]) then
		ScanSpells(bookType)
	end
	
	-- Inner-loop variables
	local startTime, duration, hasCooldown
	
	-- For each cache/saved spell...
	for spellName, spellInfo in pairs(spellCache[bookType]) do
		-- Make sure spell still available
		if (SpellFound(bookType, spellName)) then
			-- ... get spell info
			startTime, duration, hasCooldown = GetSpellCooldown(spellInfo.spellID, bookType)

			-- if cooldown is started, start timer
			if (hasCooldown == 1 and startTime ~= 0 and duration > minDuration and spellInfo.startTime ~= startTime) then
				-- Update startTime
				spellInfo.startTime = startTime
				
				-- Shedule event with duration and trigger event
				if (bookType == BOOKTYPE_SPELL and hasStartCallback) then
					LibCooldownSpell.events:Fire(
						"LibCooldownSpell_Start", 
						spellName,
						spellInfo.spellID,
						startTime,
						duration
					)
				elseif (bookType == BOOKTYPE_PET and hasPetStartCallback) then
					LibCooldownSpell.events:Fire(
						"LibCooldownPetSpell_Start", 
						spellName,
						spellInfo.spellID,
						startTime,
						duration
					)
				end
				
				sheduledSpells[bookType][spellName] = true
				SimpleTimer:ScheduleTimer(
					"LibCooldownSpell_Shedule_" .. bookType .. "_" .. spellName, 
					RelayEndEvent, 
					startTime - GetTime() + duration, 
					bookType, 
					spellName,
					spellInfo.spellID,
					spellInfo.startTime,
					duration
				)
			end
		end
	end
end


-- Make sure firing event is still a valid task
RelayEndEvent = function(bookType, spellName, spellID, startTime, duration)
	-- Make sure spell is still available
	if (sheduledSpells[bookType][spellName] and SpellFound(bookType, spellName)) then
		-- Trigger event
		if (bookType == BOOKTYPE_SPELL and hasStopCallback) then
			LibCooldownSpell.events:Fire(
				"LibCooldownSpell_End", 
				spellName,
				spellID,
				startTime,
				duration
			)
		elseif (bookType == BOOKTYPE_PET and hasPetStopCallback) then
			LibCooldownSpell.events:Fire(
				"LibCooldownPetSpell_End", 
				spellName,
				spellID,
				startTime,
				duration
			)
		end
	end
end


-- Check if player entered arena, so we need to reset all spell cooldowns
CheckZoneReset = function ()
	-- Fetch current zone
	local zone = GetRealZoneText()
	
	-- We'll just have to wait a few seconds
	if (not zone) then
		SimpleTimer:ScheduleTimer(CheckZoneReset, 5)
		
	-- Delete cooldownEnd shedules
	elseif (arenas[zone]) then
		for bookType, bookSpells in pairs(sheduledSpells) do
			-- Cancel all Timers
			for spellName, _ in pairs(bookSpells) do
				SimpleTimer:CancelTimer("LibCooldownSpell_Shedule_" .. bookType .. "_" .. spellName)
			end
			
			-- Remove from watch-list
			sheduledSpells[bookType] = {}
		end
	end
end

end


-------------------------------------------------------------------------------
-- Library - Event Handling
-------------------------------------------------------------------------------			
-- Make sure to only register events when someone is listening (someone is listening now)
function LibCooldownSpell.events:OnUsed(target, eventName)
	-- Save state
	if (eventName == "LibCooldownSpell_Start") then
		hasStartCallback = true
	elseif (eventName == "LibCooldownSpell_End") then
		hasStopCallback = true
	elseif (eventName == "LibCooldownPetSpell_Start") then
		hasPetStartCallback = true
	elseif (eventName == "LibCooldownPetSpell_End") then
		hasPetStopCallback = true
	end
	
	-- Register required events (user only)
	if (eventName == "LibCooldownSpell_Start" or eventName == "LibCooldownSpell_End") then
		frame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
	end

	-- Register required events (pet only)
	if (eventName == "LibCooldownPetSpell_Start" or eventName == "LibCooldownPetSpell_End") then
		frame:RegisterEvent("PET_BAR_UPDATE_COOLDOWN")
	end
	
	-- Register required events (user and pet)
	if (eventName == "LibCooldownPetSpell_Start" or eventName == "LibCooldownPetSpell_End" or
		eventName == "LibCooldownSpell_Start"    or eventName == "LibCooldownSpell_End") then
		frame:SetScript("OnEvent", function ()
			if (event == "SPELL_UPDATE_COOLDOWN") then
				UpdateSpellCooldown(BOOKTYPE_SPELL)
			elseif (event == "PET_BAR_UPDATE_COOLDOWN") then
				UpdateSpellCooldown(BOOKTYPE_PET)
			elseif (event == "SPELLS_CHANGED") then
				if (not SimpleTimer:IsTimerScheduled("LibCooldownSpell_Shedule_Scan_" .. BOOKTYPE_SPELL)) then
					if (GetTime() > lastUpdate + updateDelay) then
						ScanSpells(BOOKTYPE_SPELL)
						ScanSpells(BOOKTYPE_PET)
					else
						lastUpdate = GetTime()
						
						SimpleTimer:ScheduleTimer(
							"LibCooldownSpell_Shedule_Scan_" .. BOOKTYPE_SPELL, 
							ScanSpells, 
							updateDelay,
							BOOKTYPE_SPELL
						)
						SimpleTimer:ScheduleTimer(
							"LibCooldownSpell_Shedule_Scan_" .. BOOKTYPE_PET, 
							ScanSpells, 
							updateDelay,
							BOOKTYPE_PET
						)
					end
				end
			elseif(event == "ZONE_CHANGED_NEW_AREA") then
				CheckZoneReset()
			end
		end)
		frame:RegisterEvent("SPELLS_CHANGED")
		frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	end
end


-- Make sure to only register events when someone is listening (no one is listening now)
function LibCooldownSpell.events:OnUnused(target, eventName)
	-- Update state
	if (eventName == "LibCooldownSpell_Start") then
		hasStartCallback = false
	elseif (eventName == "LibCooldownSpell_End") then
		hasStopCallback = false
	elseif (eventName == "LibCooldownPetSpell_Start") then
		hasPetStartCallback = false
	elseif (eventName == "LibCooldownPetSpell_End") then
		hasPetStopCallback = false
	end
	
	-- Unregister events
	if (not hasStartCallback and not hasStopCallback and
		not hasPetStartCallback and not hasPetStopCallback) then
		frame:SetScript("OnEvent", nil)
		frame:UnregisterEvent("SPELL_UPDATE_COOLDOWN")
		frame:UnregisterEvent("SPELLS_CHANGED")
		frame:UnregisterEvent("PET_BAR_UPDATE_COOLDOWN")
		frame:UnregisterEvent("ZONE_CHANGED_NEW_AREA")
		frame:UnregisterAllEvents()
	end
	if (not hasStartCallback and not hasStopCallback) then
		frame:UnregisterEvent("SPELL_UPDATE_COOLDOWN")
	end
	if (not hasPetStartCallback and not hasPetStopCallback) then
		frame:UnregisterEvent("PET_BAR_UPDATE_COOLDOWN")
	end
end


-------------------------------------------------------------------------------
-- Library - Documentation
-------------------------------------------------------------------------------	
--[[

Events fired:
	"LibCooldownSpell_Start" (
		spellName,				= Name of triggered spell
		spellID,				= ID of spell in spellbook (as used by GetSpellName(spellID, bookType)
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
	)
	
	"LibCooldownPetSpell_Start" (
		spellName,				= Name of triggered spell
		spellID,				= ID of spell in spellbook (as used by GetSpellName(spellID, bookType)
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
	)
	
	"LibCooldownSpell_End" (
		spellName,				= Name of triggered spell
		spellID,				= ID of spell in spellbook (as used by GetSpellName(spellID, bookType)
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
	)

	"LibCooldownPetSpell_End" (
		spellName,				= Name of triggered spell
		spellID,				= ID of spell in spellbook (as used by GetSpellName(spellID, bookType)
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
	)
	
Register Events:
	CDS = LibStub:GetLibrary("LibCooldown-Spell-2.0")
	CDS:RegisterCallback(
		"EVENTNAME", 
		CALLBACKFUNTON
	)
	
EOF]]--